#include "StdAfx.h"
#include "Mesh.h"
#include "Graphics.h"
#include "SLList.h"
#include "AllocateMeshHeirarchy.h"




namespace Renderer
{
	Mesh::Mesh() : graphics(NULL), numMeshes(0), meshes(0), numFrames(0), frames(0)
	{
	}

	Mesh::~Mesh()
	{
		Free();
	}

	D3DXFRAME* Mesh::FindNodeWithMesh(D3DXFRAME* frame)
	{
		if( frame->pMeshContainer )
			if( frame->pMeshContainer->MeshData.pMesh != 0 )
				return frame;

		D3DXFRAME* f = 0;
		if(frame->pFrameSibling)
			if( f = FindNodeWithMesh(frame->pFrameSibling) )	
				return f;

		if(frame->pFrameFirstChild)
			if( f = FindNodeWithMesh(frame->pFrameFirstChild) )
				return f;

		return 0;
	}

	bool Mesh::Load(Graphics * graphics, char * fileName, char * texturePath)
	{
		AllocateMeshHeirarchy allocMeshHeirarchy;

		HR(D3DXLoadMeshHierarchyFromX(fileName, D3DXMESH_SYSTEMMEM, graphics->GetDeviceCOM(), &allocMeshHeirarchy, NULL, &rootFrame, &animationController));

		D3DXFRAME * frame = FindNodeWithMesh(rootFrame);
		if(frame == NULL)
		{
			HR(E_FAIL);
			return false;
		}
		D3DXMESHCONTAINER * meshContainer = frame->pMeshContainer;
		skinInfo = meshContainer->pSkinInfo;
		skinInfo->AddRef();

		numBones = meshContainer->pSkinInfo->GetNumBones();
		transformMatrices.reserve(numBones);
		combinedMatrices.reserve(numBones);
		for(DWORD i = 0; i < numBones; ++i)
			combinedMatrices[i] = NULL;

		BuildMesh(meshContainer->MeshData.pMesh);
			return true;
	}

	void Mesh::BuildMesh(ID3DXMesh* mesh)
	{
		
	}

	/*bool Mesh::Load(Graphics *graphics, char *fileName, char *texturePath)
	{
	ID3DXFile * DXFile = NULL;
	ID3DXFileEnumObject * DXEnum = NULL;
	ID3DXFileData * DXData = NULL;
	sFrame *parent, * framePtr;
	sMesh * mesh;

	Free();

	this->graphics = graphics;
	if(graphics == NULL || fileName == NULL)
	return false;

	if(FAILED(D3DXFileCreate(&DXFile)))
	return false;

	if(FAILED(DXFile->RegisterTemplates((void*)bob,D3DRM_XTEMPLATE_BYTES)))
	{
	DXFile->Release();
	return false;
	}

	if(FAILED(DXFile->CreateEnumObject((LPCVOID)fileName, D3DXF_FILELOAD_FROMFILE, &DXEnum)))
	{
	DXFile->Release();
	return false;
	}

	parent = new sFrame();
	SIZE_T size;
	DXEnum->GetChildren(&size);
	for(SIZE_T i = 0; i < size; ++i)
	{
	if(SUCCEEDED(DXEnum->GetChild(i, &DXData)))
	{
	ParseXFileData(DXData, parent, texturePath);
	ReleaseCOM(DXData);
	}
	}

	ReleaseCOM(DXEnum);
	ReleaseCOM(DXFile);

	if(parent->meshList.size() != 0)
	{
	frames = parent;
	frames->name = new char[7];
	strcpy_s(frames->name, 7, "%ROOT%");
	}
	else
	{
	frames = parent->child;
	framePtr = frames;
	while(framePtr != NULL)
	{
	framePtr->parent = NULL;
	framePtr = framePtr->sibling;
	}
	parent->child = NULL;
	delete parent;
	}

	MapFramesToBones(frames);

	mesh = this->meshes;
	if(mesh != NULL)
	{
	while(mesh != NULL)
	{
	boundingBox.minPoint.x = min(boundingBox.minPoint.x, mesh->boundingBox.minPoint.x);
	boundingBox.minPoint.y = min(boundingBox.minPoint.y, mesh->boundingBox.minPoint.y);
	boundingBox.minPoint.z = min(boundingBox.minPoint.z, mesh->boundingBox.minPoint.z);
	boundingBox.maxPoint.x = max(boundingBox.maxPoint.x, mesh->boundingBox.minPoint.x);
	boundingBox.maxPoint.y = max(boundingBox.maxPoint.y, mesh->boundingBox.minPoint.y);
	boundingBox.maxPoint.z = max(boundingBox.maxPoint.z, mesh->boundingBox.minPoint.z);
	boundingSphere.radius = max(boundingSphere.radius, mesh->boundingSphere.radius);		

	mesh = mesh->next;
	}
	}

	return true;
	}*/


	void Mesh::ParseXFileData(ID3DXFileData *data, Renderer::sFrame *parentFrame, char *texturePath)
	{
		ID3DXFileData * subObj = NULL;
		ID3DXFileData * subData = NULL;
		//ID3DXFileData * dataRef;
		GUID typeGUID;
		char * name = NULL;
		DWORD size;
		//sFrame * subFrame = NULL;
		char path[MAX_PATH];

		sFrame * frame = NULL;
		D3DXMATRIX * frameMatrix = NULL;

		sMesh * mesh = NULL;
		ID3DXBuffer * materialBuffer = NULL;
		D3DXMATERIAL * materials = NULL;
		ID3DXBuffer * adjacency = NULL;

		BYTE ** ptr;

		if(FAILED(data->GetType(&typeGUID)))
			return;

		if(FAILED(data->GetName(NULL, &size)))
			return;

		if(size != 0)
		{
			name = new char[size];
			if(name != NULL)
				data->GetName(name, &size);
		}

		if(name == NULL || strlen(name) == 0)
		{
			delete [] name;
			name = new char[9];
			if(name == NULL)
				return;
			strcpy_s(name, 9, "$NoName$");
		}



		if(typeGUID == TID_D3DRMFrame)
		{
			frame = new sFrame();

			frame->name = name;
			name = NULL;

			frame->parent = parentFrame;
			frame->sibling = parentFrame->child;
			parentFrame->child = frame;

			numFrames++;
		}
		else if(typeGUID == TID_D3DRMFrameTransformMatrix)
		{
			BYTE** dataPtr = NULL;

			if(FAILED(data->Lock(&size, (LPCVOID*)&dataPtr)))
				return;

			memcpy(&parentFrame->originalMatrix, (void*)dataPtr, size);
			data->Unlock();
		}
		else if(typeGUID == TID_D3DRMMesh)
		{
			if(meshes == NULL || meshes->FindMesh(name) == NULL)
			{
				mesh = new sMesh();

				mesh->name = name;
				name = NULL;


				//load mesh info
				if(FAILED(D3DXLoadSkinMeshFromXof(data, 0, graphics->GetDeviceCOM(), &adjacency, &materialBuffer, NULL, &mesh->numMaterials, &mesh->skinInfo, &mesh->mesh)))
				{
					delete mesh;
					return;
				}

				//Get bounding Info
				if(SUCCEEDED(mesh->mesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&ptr)))
				{
					D3DXComputeBoundingBox((D3DXVECTOR3*)ptr, mesh->mesh->GetNumVertices(),
						mesh->mesh->GetNumBytesPerVertex(), &mesh->boundingBox.minPoint, &mesh->boundingBox.maxPoint);
					D3DXComputeBoundingSphere((D3DXVECTOR3*)ptr, mesh->mesh->GetNumVertices(),
						mesh->mesh->GetNumBytesPerVertex(), &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &mesh->boundingSphere.radius);

					mesh->mesh->UnlockVertexBuffer();
				}

				//Set number of bones in mesh
				if(mesh->skinInfo)
					mesh->numBones = mesh->skinInfo->GetNumBones();

				//Get mesh's Vertex Format
				if(mesh->skinInfo != NULL && mesh->numBones != 0)
				{
					if(FAILED(mesh->mesh->CloneMeshFVF(0, mesh->mesh->GetFVF(), graphics->GetDeviceCOM(), &mesh->skinMesh)))
						ReleaseCOM(mesh->skinInfo);

				}

				if(mesh->skinInfo != NULL && mesh->numBones != 0)
				{
					mesh->boneMatrices = new D3DXMATRIX[mesh->numBones];
					for(DWORD i = 0; i < mesh->numBones; ++i)
						D3DXMatrixIdentity(&mesh->boneMatrices[i]);

					mesh->frameMatrices = new D3DXMATRIX*[mesh->numBones];
					for(DWORD i = 0; i < mesh->numBones; ++i)
						mesh->frameMatrices[i] = NULL;
				}

				if(mesh->numMaterials == 0)
				{
					mesh->materials = new D3DMATERIAL9[1];
					mesh->textures = new LPDIRECT3DTEXTURE9[1];

					ZeroMemory(mesh->materials, sizeof(D3DMATERIAL9));
					mesh->materials[0].Diffuse.r = 1.0f;
					mesh->materials[0].Diffuse.g = 1.0f;
					mesh->materials[0].Diffuse.b = 1.0f;
					mesh->materials[0].Diffuse.a = 1.0f;
					mesh->materials[0].Ambient = mesh->materials[0].Diffuse;
					mesh->materials[0].Specular = mesh->materials[0].Diffuse;
					mesh->textures[0] = NULL;

					mesh->numMaterials = 1;
				}
				else
				{
					materials = (D3DXMATERIAL*)materialBuffer->GetBufferPointer();
					mesh->materials = new D3DMATERIAL9[mesh->numMaterials];
					mesh->textures = new LPDIRECT3DTEXTURE9[mesh->numMaterials];

					for(DWORD i = 0; i < mesh->numMaterials; ++i)
					{
						mesh->materials[i] = materials[i].MatD3D;
						mesh->materials[i].Ambient = mesh->materials[i].Diffuse;

						sprintf_s(path, "%s%s", texturePath, materials[i].pTextureFilename);
						if(FAILED(D3DXCreateTextureFromFile(graphics->GetDeviceCOM(), path, &mesh->textures[i])))
						{
							mesh->textures[i] = NULL;
						}
					}
				}
				ReleaseCOM(materialBuffer);

				mesh->next = this->meshes;
				this->meshes = mesh;
				numMeshes++;
			}
			else
			{
				mesh = meshes->FindMesh(name);
			}

			if(mesh != NULL)
			{
				parentFrame->AddMesh(mesh);
			}
		}
		else if(typeGUID == TID_D3DRMAnimationSet || typeGUID == TID_D3DRMAnimation || typeGUID == TID_D3DRMAnimationKey)
		{
			delete [] name;
			return;
		}

		delete [] name;

		SIZE_T numChildren;
		data->GetChildren(&numChildren);
		for(SIZE_T i = 0; i < numChildren; ++i)
		{
			HR(data->GetChild(i, &subObj));
			if(subObj != NULL)
			{
				ParseXFileData(subObj, frame, texturePath);
			}
			ReleaseCOM(subObj);
		}

		return;
	}

	void Mesh::MapFramesToBones(Renderer::sFrame *frame)
	{
		sMesh * mesh;

		if(frame == NULL || frame->name == NULL)
			return;

		mesh = this->meshes;

		while(mesh != NULL)
		{
			if(mesh->skinInfo && mesh->numBones && mesh->boneMatrices != NULL && mesh->frameMatrices != NULL)
			{
				for(DWORD i = 0; i < mesh->numBones; ++i)
				{
					if(strcmp(frame->name, mesh->skinInfo->GetBoneName(i)) == 0)
					{
						mesh->frameMatrices[i] = &frame->combinedMatrix;
						break;
					}
				}
			}
			mesh = mesh->next;
		}

		MapFramesToBones(frame->child);

		MapFramesToBones(frame->sibling);
	}

	void Mesh::Free()
	{
		graphics = NULL;
		numMeshes = 0;
		delete meshes;
		meshes = NULL;

		numFrames = 0;
		delete frames;
		frames = NULL;

		boundingBox.maxPoint = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
		boundingSphere.radius = 0.0f;
	}

	bool Mesh::IsLoaded()
	{
		return (meshes != NULL && frames != NULL);
	}

	int Mesh::GetNumMeshes()
	{
		return numMeshes;
	}

	int Mesh::GetNumFrames()
	{
		return numFrames;
	}

	sFrame * Mesh::GetParentFrame()
	{
		return frames;
	}

	sFrame * Mesh::GetFrame(char * name)
	{
		if(frames == NULL)
			return NULL;

		return frames->FindFrame(name);
	}

	sMesh * Mesh::GetParentMesh()
	{
		return meshes;
	}

	sMesh * Mesh::GetMesh(char *name)
	{
		if(meshes == NULL)
			return NULL;

		return meshes->FindMesh(name);
	}

	void Mesh::GetBounds(float *minX, float *minY, float *minZ, float *maxX, float *maxY, float *maxZ, float *radius)
	{
		if(minX != NULL)
			*minX = boundingBox.minPoint.x;
		if(minY != NULL)
			*minY = boundingBox.minPoint.y;
		if(minY != NULL)
			*minZ = boundingBox.minPoint.z;

		if(maxY != NULL)
			*maxY = boundingBox.maxPoint.x;
		if(maxY != NULL)
			*maxY = boundingBox.maxPoint.y;
		if(maxY != NULL)
			*maxY = boundingBox.maxPoint.z;

		if(radius != NULL)
			*radius = boundingSphere.radius;
	}

	Animation::Animation() : numAnimations(0) , animationSet(NULL) {}

	Animation::~Animation()
	{
		Free();
	}

	bool Animation::Load(char *fileName, Renderer::Mesh *mapMesh)
	{
		ID3DXFile * DXFile = NULL;
		ID3DXFileEnumObject * DXEnum = NULL;
		ID3DXFileData * DXData = NULL;

		Free();

		if(fileName == NULL)
			return false;

		if(FAILED(D3DXFileCreate(&DXFile)))
			return false;

		if(FAILED(DXFile->RegisterTemplates((void*)bob,D3DRM_XTEMPLATE_BYTES)))
		{
			DXFile->Release();
			return false;
		}

		if(FAILED(DXFile->CreateEnumObject((void*)fileName, D3DXF_FILELOAD_FROMFILE, &DXEnum)))
		{
			DXFile->Release();
			return false;
		}

		SIZE_T numChildren;
		DXEnum->GetChildren(&numChildren);
		for(SIZE_T i = 0;i < numChildren; ++i)
		{
			HR(DXEnum->GetChild(i, &DXData));
			ParseXFileData(DXData, NULL, NULL);
			ReleaseCOM(DXData);
		}

		ReleaseCOM(DXEnum);
		ReleaseCOM(DXFile);

		if(mapMesh != NULL)
			MapToMesh(mapMesh);

		return true;
	}

	void Animation::ParseXFileData(ID3DXFileData *data, Renderer::sAnimationSet *parentAnim, Renderer::sAnimation *currentAnim)
	{
		ID3DXFileData * subData = NULL;
		GUID typeGUID;
		char * name = NULL;
		DWORD size;
		PBYTE * dataPtr;

		DWORD keyType, numKeys, time;
		sXFileRotateKey * rotateKey;
		sXFileScaleKey * scaleKey;
		sXFilePositionKey * positionKey;
		sXFileMatrixKey * matrixKey;

		sAnimationSet * subAnimSet = NULL;
		sAnimation * subAnim = NULL;
		sAnimation * anim = NULL;
		sAnimationSet * animSet = NULL;

		HR(data->GetType(&typeGUID));
		if(FAILED(data->GetType(&typeGUID)))
			return;

		if(FAILED(data->GetName(NULL, &size)))
			return;

		if(size != 0)
		{
			name = new char[size];
			data->GetName(name, &size);
		}

		if(name == NULL)
		{
			name = new char[9];
			strcpy_s(name, 9, "$NoName$");
		}

		subAnimSet = parentAnim;
		subAnim = currentAnim;

		if(typeGUID == TID_D3DRMAnimationSet)
		{
			animSet = new sAnimationSet();
			if(animSet == NULL)
				return;

			animSet->name = name;
			name = NULL;

			animSet->next = this->animationSet;
			this->animationSet = animSet;

			subAnimSet = animSet;
		}
		else if(typeGUID == TID_D3DRMAnimation && parentAnim != NULL)
		{
			anim = new sAnimation();
			if(anim == NULL)
				return;

			anim->name = name;
			name = NULL;

			anim->next = parentAnim->animation;
			parentAnim->animation = anim;

			subAnim = anim;

		}
		else if(typeGUID == TID_D3DRMAnimationKey && currentAnim != NULL)
		{
			BYTE** dataPtr = NULL;

			if (FAILED(data->Lock(&size, (LPCVOID*)&dataPtr)))
				return;

			keyType = ((DWORD*)dataPtr)[0];
			numKeys = ((DWORD*)dataPtr)[1];

			switch(keyType) //Rotation Key
			{
			case 0:
				delete [] currentAnim->rotateKeys;
				currentAnim->rotateKeys = new sRotateKey[numKeys];
				if(currentAnim->rotateKeys == NULL)
					return;
				currentAnim->numRotateKeys = numKeys;
				rotateKey = (sXFileRotateKey*)((char*)dataPtr + (sizeof(DWORD) * 2));

				for(DWORD i = 0; i < numKeys; ++i)
				{
					currentAnim->rotateKeys[i].time = rotateKey->time;
					currentAnim->rotateKeys[i].quaternion.x = -rotateKey->x;
					currentAnim->rotateKeys[i].quaternion.y = -rotateKey->y;
					currentAnim->rotateKeys[i].quaternion.z = -rotateKey->z;
					currentAnim->rotateKeys[i].quaternion.w = rotateKey->w;

					if(rotateKey->time > parentAnim->length)
						parentAnim->length = rotateKey->time;

					rotateKey += 1;
				}

				break;

			case 1: //Scale Key
				delete [] currentAnim->scaleKeys;
				currentAnim->scaleKeys = new sScaleKey[numKeys];
				if(currentAnim->scaleKeys == NULL)
					return;
				currentAnim->numScaleKeys = numKeys;

				scaleKey = (sXFileScaleKey*)((char*)dataPtr + (sizeof(DWORD) * 2));
				for(DWORD i = 0; i < numKeys; ++i)
				{
					currentAnim->scaleKeys[i].time = scaleKey->time;
					currentAnim->scaleKeys[i].scale = scaleKey->scale;

					if(scaleKey->time > parentAnim->length)
						parentAnim->length = scaleKey->time;

					scaleKey += 1;
				}

				if(numKeys > 1)
				{
					for(DWORD i = 0; i < numKeys - 1; ++i)
					{
						currentAnim->scaleKeys[i].scaleInterpolation = currentAnim->scaleKeys[i + 1].scale - currentAnim->scaleKeys[i].scale;
						time = currentAnim->scaleKeys[i + 1].time - currentAnim->scaleKeys[i].time;
						if(time == 0)
							time = 1;
						currentAnim->scaleKeys[i].scaleInterpolation /= (float)time;
					}
				}

				break;

			case 2: //Position Key
				delete [] currentAnim->positionKeys;
				currentAnim->positionKeys = new sPositionKey[numKeys];

				currentAnim->numPositionKeys = numKeys;
				positionKey = (sXFilePositionKey*)((char*)dataPtr + (sizeof(DWORD) * 2));
				for(DWORD i = 0; i < numKeys; ++i)
				{
					currentAnim->positionKeys[i].time = positionKey->time;
					currentAnim->positionKeys[i].Pos = positionKey->pos;

					if(positionKey->time > parentAnim->length)
						parentAnim->length = positionKey->time;

					positionKey += 1;
				}

				if(numKeys > 1)
				{
					for(DWORD i = 0; i < numKeys - 1; ++i)
					{
						currentAnim->positionKeys[i].PosInterpolation = currentAnim->positionKeys[i + 1].Pos - currentAnim->positionKeys[i].Pos;
						time = currentAnim->positionKeys[i + 1].time - currentAnim->positionKeys[i].time;
						if(time == 0)
							time = 1;
						currentAnim->positionKeys[i].PosInterpolation /= (float)time;
					}
				}

				break;

			case 4: //Matrix Key
				delete [] currentAnim->matrixKeys;
				currentAnim->matrixKeys = new sMatrixKey[numKeys];
				currentAnim->numMatrixKeys = numKeys;
				matrixKey = (sXFileMatrixKey*)((char*)dataPtr + (sizeof(DWORD) * 2));
				for(DWORD i = 0; i < numKeys; ++i)
				{
					currentAnim->matrixKeys[i].time = matrixKey->time;
					currentAnim->matrixKeys[i].matrix = matrixKey->matrix;

					if(matrixKey->time > parentAnim->length)
						parentAnim->length = matrixKey->time;

					matrixKey += 1;
				}

				if(numKeys > 1)
				{
					for(DWORD i = 0; i < numKeys - 1; ++i)
					{
						currentAnim->matrixKeys[i].interpolationMatrix = currentAnim->matrixKeys[i + 1].matrix - currentAnim->matrixKeys[i].matrix;
						time = currentAnim->matrixKeys[i + 1].time - currentAnim->matrixKeys[i].time;

						if(time == 0)
							time = 1;
						currentAnim->matrixKeys[i].interpolationMatrix /= (float)time;
					}
				}
				break;
			}
		}
		else if(typeGUID == TID_D3DRMAnimationOptions && currentAnim != NULL)
		{
			BYTE** DataPtr = NULL;

			if (FAILED(data->Lock(&size, (LPCVOID*)&dataPtr)))
				return;

			if(((DWORD*)dataPtr)[0] == 0)
				currentAnim->loop = true;
			else
				currentAnim->loop = false;

			if(((DWORD*)dataPtr[1]) == 0)
				currentAnim->linear = false;
			else
				currentAnim->linear = true;
		}
		else if(typeGUID == TID_D3DRMFrame && currentAnim != NULL)
		{
			currentAnim->frameName = name;
			return;
		}

		delete [] name;

		SIZE_T numChildren;
		data->GetChildren(&numChildren);
		//while(SUCCEEDED(data->GetNextObject(&subObj)))
		ID3DXFileData * subObj;
		for(SIZE_T i = 0; i < numChildren; ++i)
		{
			data->GetChild(i, &subObj);

			ParseXFileData(subObj, subAnimSet, subAnim);
			ReleaseCOM(subObj);
		}
	}

	void Animation::Free()
	{
		delete animationSet;
		animationSet = NULL;
		numAnimations = 0;
	}

	bool Animation::IsLoaded()
	{
		return animationSet == NULL;
	}

	bool Animation::MapToMesh(Mesh * mesh)
	{
		sAnimationSet * animSet;
		sAnimation * anim;

		if(mesh == NULL)
			return false;

		animSet = this->animationSet;
		if(animSet == NULL)
			return false;

		while(animSet != NULL)
		{
			anim = animSet->animation;
			while(anim != NULL)
			{
				anim->frame = mesh->GetFrame(anim->frameName);
				anim = anim->next;
			}
			animSet = animSet->next;
		}

		return true;
	}

	int Animation::GetNumAnimations()
	{
		return numAnimations;
	}

	sAnimationSet * Animation::GetAnimationSet(char *name)
	{
		if(animationSet == NULL)
			return NULL;

		return animationSet->FindSet(name);
	}

	bool Animation::SetLoop(bool toLoop, char * name)
	{
		sAnimationSet * animSet;
		sAnimation * anim;

		animSet = GetAnimationSet(name);
		if(animSet == NULL)
			return false;

		anim = animSet->animation;
		while(anim != NULL)
		{
			anim->loop = toLoop;
			anim = anim->next;
		}

		return true;
	}

	ULONG Animation::GetLength(char * name)
	{
		sAnimationSet * animSet;

		animSet = GetAnimationSet(name);
		if(animSet == NULL)
			return 0;
		return animSet->length;
	}

}